#!/usr/bin/python
# Copyright (c) 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import logging
import logging.handlers
import os
import re
import subprocess
import sys
import time

import uploader_mail

# How many seconds to wait after a cycle.
CYCLE_DELAY = 600

# Location to log to.
LOG_PATH = '/uploader/logs/uploader.log'

# Directory containing uploader code
UPLOADER_PATH = '/home/uploader/chrome-devtools-frontend/gce'

# Dictionary mapping constant names to exit codes
EXIT_CODES = {}

# Dictionary mapping exit codes to human readable descriptions
EXIT_CODES_TEXT = {}

def ParseExitCodeList(path):
  skip_line = re.compile('^\s*(#|\n)')
  match_line = re.compile('^(\w+)=(\d+)\s*#\s*(.*)\n')
  with open(path) as f:
    for line in f:
      if skip_line.match(line):
        continue
      match = match_line.match(line)
      name, code, description = match.groups()
      code = int(code)
      EXIT_CODES[name] = code
      EXIT_CODES_TEXT[code] = description

def CallAndLog(args):
  process = subprocess.Popen(args, stdout=subprocess.PIPE,
                             stderr=subprocess.STDOUT)
  # Read lines explicitly like this because iterating over the file uses a large
  # buffer internally and makes the log update very slowly.
  while True:
    line = process.stdout.readline()
    if not line:
      break
    logging.debug(line.rstrip('\n'))
  return process.wait()

def CallAndGetOutput(args):
  process = subprocess.Popen(args, stdout=subprocess.PIPE,
                             stderr=subprocess.STDOUT)
  out, err = process.communicate()
  if process.returncode != 0:
    logging.error('Error %d from %s', process.returncode, args)
  return out

def main():
  """Main loop of the uploader."""

  ParseExitCodeList(os.path.join(UPLOADER_PATH, 'uploader_exit_codes.sh'))
  mail_config = uploader_mail.ParseMailConfig(CallAndGetOutput(['gsutil', 'cat', 'gs://chrome-devtools-frontend/mail_config']))

  logger = logging.getLogger()
  logger.setLevel(logging.DEBUG)

  console = logging.StreamHandler()
  console.setLevel(logging.INFO)
  logger.addHandler(console)

  logfile = logging.handlers.RotatingFileHandler(LOG_PATH, backupCount=30)
  logfile.setLevel(logging.DEBUG)
  logger.addHandler(logfile)

  formatter = logging.Formatter('%(asctime)s:%(message)s',
                                datefmt='%Y-%m-%d %H:%M:%S')
  console.setFormatter(formatter)
  logfile.setFormatter(formatter)

  keep_going = True
  # Initialise last_status to something invalid so that the first run will
  # send an "all clear" mail if it succeeds.
  last_status = -1
  status_repeats = 0

  while keep_going:
    logfile.doRollover()
    logging.info('Starting uploader cycle...')
    r = CallAndLog([os.path.join(UPLOADER_PATH, 'uploader_iteration.sh')])
    if r == 0 or r == EXIT_CODES['EXIT_NO_UPDATED_BRANCHES']:
      if r == 0:
        logging.info('Cycle succeeded.')
      else:
        logging.info('No new changes.')
      if last_status != 0:
        uploader_mail.SendAllClearMail(mail_config)
        logging.info('All clear mail sent.')
      last_status = status_repeats = 0
      if r != 0:
        time.sleep(CYCLE_DELAY)
    else:
      reason = EXIT_CODES_TEXT.get(r, 'unknown error %d' % r)
      logging.error('Error encountered: iteration returned %d (%s)', r, reason)
      if last_status == r:
        status_repeats += 1
      else:
        last_status = r
        status_repeats = 0
      # Don't send email constantly on repeated errors.
      if status_repeats % 10 == 0:
        uploader_mail.SendErrorMail(mail_config, reason, status_repeats + 1, LOG_PATH)
        logging.info('Mail sent.')
      # Wait before trying again.
      time.sleep(CYCLE_DELAY)

  return 0


if __name__ == '__main__':
  sys.exit(main())
